/*
 *  Copyright (c) 2001 Sun Microsystems, Inc.  All rights
 *  reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *  1. Redistributions of source code must retain thene above copyright
 *  notice, this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in
 *  the documentation and/or other materials provided with the
 *  distribution.
 *
 *  3. The end-user documentation included wpith the redistribution,
 *  if any, must include the following acknowledgment:
 *  "This product includes software developed by the
 *  Sun Microsystems, Inc. for Project JXTA."
 *  Alternately, this acknowledgment may appear in the software itself,
 *  if and wherever such third-party acknowledgments normally appear.
 *
 *  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
 *  must not be used to endorse or promote products derived from this
 *  software without prior written permission. For written
 *  permission, please contact Project JXTA at http://www.jxta.org.
 *
 *  5. Products derived from this software may not be called "JXTA",
 *  nor may "JXTA" appear in their name, without prior written
 *  permission of Sun.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 *  ====================================================================
 *
 *  This software consists of voluntary contributions made by many
 *  individuals on behalf of Project JXTA.  For more
 *  information on Project JXTA, please see
 *  <http://www.jxta.org/>.
 *
 *  This license is based on the BSD license adopted by the Apache Foundation.
 *
 *  $Id: ShareManager.java,v 1.2 2007/04/11 20:26:28 nano Exp $
 */

package net.jxta.myjxta.plugins.share;

import net.jxta.exception.PeerGroupException;
import net.jxta.logging.Logging;
import net.jxta.myjxta.MyJXTA;
import net.jxta.myjxta.share.IShareManager;
import net.jxta.myjxta.util.*;
import net.jxta.myjxta.util.objectmodel.ShareNode;
import net.jxta.peergroup.PeerGroup;
import net.jxta.share.CMS;
import net.jxta.share.Content;
import net.jxta.share.ContentManager;
import net.jxta.share.FileContent;

import java.io.*;
import java.net.URI;
import java.net.URLEncoder;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author james todd [gonzo at jxta dot org]
 * @version $Id: ShareManager.java,v 1.2 2007/04/11 20:26:28 nano Exp $
 */

public class ShareManager implements IShareManager {

    /**
     * The block size used if we copy files
     */
    private static final int BLOCK = 4 * 1024;
    private static final String DELIMITER = "-";
    private static final ResourceBundle STRINGS = Resources.getStrings();
    private static final Logger LOG = Logger.getLogger(ShareManager.class.getName());

    /**
     * The hashmap that contains the ShareManager
     * for the specific peergroup
     */
    private static Map<String, ShareManager> managers = null;

    /**
     * The directory containing the cms information
     */
    private static File cms = null;

    /**
     * The directory that contains the shared files
     */
    private static File shares = null;

    /**
     * The ShareMonitor that unshares and shares files
     */
    private ShareMonitor monitor = null;

    /**
     * The peer group specific directory for the shared files
     */
    private File share = null;

    /**
     * The MyJXTA instance for use for application wide parameters
     */
    private MyJXTA myjxta = null;

    /**
     * The ContentManager that manages locally shared content
     */
    private ContentManager contentManager = null;

    /**
     * A map of our shared files - needed so that we can unshare files
     */
    private Map<File, Content> cache = null;

    /**
     * The PeerGroup in which this ShareManager works
     */
    private final Group group;
    private CMS m_contentManagementSystem;

    /**
     * Get the ShareManager for the indicated group
     *
     * @param myjxta the MyJXTA version that stores system wide parameters
     * @param g      the peer group of this ShareManager
     * @param p_create should the manager be created if none can be found in the cache?
     */
    public static synchronized ShareManager getShareManager(MyJXTA myjxta,
                                                            Group g, boolean p_create) {
        if (cms == null ||
                ! cms.exists() ||
                shares == null ||
                ! shares.exists()) {
            init();
        }

        if (managers == null) {
            managers = new HashMap<String, ShareManager>();
        }

        String id = getShareId(g);
        ShareManager manager = managers.get(id);

        if (p_create && id != null && manager == null) {
            manager = new ShareManager(myjxta, g);

            managers.put(id, manager);
        }

        return manager;
    }

    /**
     * Unshares all files previously shared by any created
     * ShareManager
     */
    public static synchronized void unpublish() {
        if (managers != null) {
            for (String s : managers.keySet()) {
                managers.get(s).stop();
            }
        }
    }

    public File getShares() {
        return this.share;
    }

    public void share(URI u) {
        File f = u != null ? new File(u) : null;

        if (f != null) {
            if (u.getScheme().equalsIgnoreCase(Constants.PROTOCOL_FILE)) {
            } else if (u.getScheme().equalsIgnoreCase(Constants.PROTOCOL_HTTP)) {
                // xxx: support http content
                // xxx: readHTML(u);
            }

            share(f);
        }
    }

    /**
     * A new file to share
     *
     * @param file the File to share
     */
    public void share(File file) {
        share(file, true);
    }

    public void share(List<File> files) {
        for (File file : files) {
            share(file);
        }
    }

    /**
     * Unshare a previously shared file
     *
     * @param file the file to unshare
     */
    public void unshare(File file) {
        unshare(file, true);
    }

    public void unshare(List<File> files) {
        for (File file : files) {
            unshare(file);
        }
    }

    public void stop() {
        if (this.monitor != null) {
            this.monitor.updateShares(false);
            this.monitor.stopThread();
        }
        if (m_contentManagementSystem != null){
        	try {
        		m_contentManagementSystem.stopApp();
        	} catch (NullPointerException e){
        		LOG.warning("CMS shutdown produced a NPE");
        	}
        }
            

    }

    /**
     * A new file to share
     *
     * @param file the file to share
     * @param copy should we copy the data to our personal share area
     */
    protected void share(File file, boolean copy) {

        if (file.isDirectory()) {
            File[] fileList = file.listFiles();
            for (File aFileList : fileList) {
                share(aFileList, true);
            }
            return;
        }

        File to  ;
        boolean isCopied = false;

        if (copy) {
            to = new File(share, file.getName());

            FileInputStream fis = null;
            FileOutputStream fos = null;

            try {
                to.createNewFile();

                fis = new FileInputStream(file);
                fos = new FileOutputStream(to);

                byte[] buf = new byte[BLOCK];
                int l  ;

                while ((l = fis.read(buf, 0, BLOCK)) > -1) {
                    fos.write(buf, 0, l);
                }

                isCopied = true;
            } catch (FileNotFoundException fnfe) {
                fnfe.printStackTrace();
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
            finally {
                if (fis != null) {
                    try {
                        fis.close();
                    } catch (IOException ioe) {
                        ioe.printStackTrace();
                    }
                }

                if (fos != null) {
                    try {
                        fos.close();
                    } catch (IOException ioe) {
                        ioe.printStackTrace();
                    }
                }
            }
        } else {
            to = file;
            isCopied = true;
        }

        // and now share the file
        if (isCopied) {
            boolean isShared = false;
            Content content = null;

            if (this.cache == null) {
                this.cache = new HashMap<File, Content>();
            }

            try {
                content = this.contentManager.share(to);
                this.cache.put(to, content);

                isShared = true;
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }

            if (isShared && content != null) {
                ShareNode shareNode = new ShareNode(new Share(content.getContentAdvertisement(),
                        to), this.group, ShareNode.LOCAL_SHARE);

                this.myjxta.addJxtaNode(shareNode);
            } else {
                this.myjxta.setStatus(STRINGS.getString("error.resource.add.invalid") + ": " + to.toString());
            }
        }
    }

    /**
     * Unshare a previously shared file
     *
     * @param file   the file to unshare
     * @param remove should we remove the file from the
     *               personal holding area
     */
    protected void unshare(File file, boolean remove) {
        FileContent fc = (this.cache != null ?
                (FileContent) this.cache.get(file) : null);
        boolean isUnshared = false;

        if (fc != null) {
            try {
                this.contentManager.unshare(fc);

                isUnshared = true;
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }

        if (isUnshared) {
            this.cache.remove(file);
        } else {
            this.myjxta.setStatus(STRINGS.getString("error.resource.add.invalid") +
                    ": " + file.toString());
        }

        if (remove && isUnshared) {
            Env.delete(new File(share, file.getName()));
        }
    }

    private static void init() {
        // create all relevant directories
        String base = System.getProperty(Env.MYJXTA, Env.getHome().getPath());

        cms = new File(base + File.separator + Env.CMS);
        shares = new File(base + File.separator + Env.SHARE);

        // xxx: seems harsh but we can ensure repub via this means
        cms.deleteOnExit();

        Env.delete(cms);
        cms.mkdirs();
        shares.mkdirs();
    }

    private static String getShareId(Group g) {
        if (null == g) {
            return null;
        }

        PeerGroup pg = g.getPeerGroup();

        if (null == pg) {
            return null;
        }

        URI u = pg.getPeerGroupID().toURI();
        String id = pg.getPeerGroupName() + DELIMITER + u.toASCIIString();
        try {
            id = URLEncoder.encode(id, "UTF-8");
        }
        catch (UnsupportedEncodingException uee) {
            if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
                LOG.log(Level.FINE, "unable to url encode: " + id, uee);
            }
        }
        return id;
    }

    /**
     * Create a new ShareManager
     *
     * @param myjxta the MyJXTA version that stores system wide parameters
     * @param g      the peer group of this ShareManager
     */
    private ShareManager(MyJXTA myjxta, Group g) {
        this.myjxta = myjxta;
        this.group = g;

        // create a directory for this peergroup
        String id = getShareId(g);

        if (id != null) {
            File work = new File(cms, id);

            Env.delete(work);
            work.mkdirs();

            this.share = new File(shares, id);

            this.share.mkdirs();

            // start the ContentManager
            m_contentManagementSystem = new CMS();

            try {
                m_contentManagementSystem.init(g.getPeerGroup(), null, null);
                m_contentManagementSystem.startApp(new File(work, "cms"));

                this.contentManager = m_contentManagementSystem.getContentManager();
            } catch (PeerGroupException pge) {
                this.myjxta.setStatus(STRINGS.getString("error.share.initialization.failure"));
            }

            // republish all previously shared files
            this.monitor = new ShareMonitor(this);

            this.monitor.updateShares(true);
        }
    }

    public void unshareAll() {
        if (this.cache != null) {
            ArrayList<File> t = new ArrayList<File>(this.cache.keySet());
            unshare(t);   
        }
    }
}
